#include <vector>
#include "loadModel.h"
#include "vec3.h"
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>

bool loadOBJ( const char *path, std::vector<vec3> &out_vertices, vec3 *bound,
	unsigned char mirror)
{
	//Open file
	FILE *file = fopen(path, "r");

	//Failed to open file
	if (!file)
	{
		printf("Error opening file: %s\n", path);
		return false;
	}

	//Vector for vertices - Here will be saved all vertices by index (from file)
	std::vector<vec3> temp_vertices;

	//Buffer
	char lineHeader[100];

	//Read each word 
	while (fscanf(file, "%s", lineHeader) != EOF)
	{
		//Found comment
		if ( lineHeader[0] == '#' )
			//Skip line
			fscanf(file, "%*[^\n]"); else
		//Found vertex
		if ( lineHeader[0] == 'v' )
		{
			//Create new vertex and get it's coordinates
			vec3 vertex;
			fscanf(file, "%f %f %f", &vertex.x, &vertex.y, &vertex.z);

			//Apply mirroring (if exists)
			if ( mirror == 1 )
				vertex.x = -vertex.x;
			if ( mirror == 2 )
				vertex.y = -vertex.y;
			if ( mirror == 4 )
				vertex.z = -vertex.z;

			//Save to vertices by index
			temp_vertices.push_back(vertex);

			//vec3 is passes for bound
			if (bound)
			{
				//This saves bound from center to positive axis
				// it isn't the best way, but for now it works
				if (bound->x < vertex.x) bound->x = vertex.x;
				if (bound->y < vertex.y) bound->y = vertex.y;
				if (bound->z < vertex.z) bound->z = vertex.z;
			}
		} else
		//Found face
		if ( lineHeader[0] == 'f' )
		{
			//Check the next three numbers
			unsigned int j;
			for (j = 0; j < 3; j++)
			{
				//Get index to vertex (from temp_vertices)
				unsigned int index;
				fscanf(file, "%d", &index);

				//Push vertex to final array. -1 because indexes start from 1 in .obj
				out_vertices.push_back(temp_vertices[ index-1 ]);
			}
		}
	} //While loop

	//Close file
	fclose(file);

	//Success!
	return true;
}

/* Opens file, reads header, 
 * parses vertices (3 floats - position, 3 bytes - color) and faces (triangles only). 
 * Toy parser only, it doesn't support all .ply files (probably) 
 */
bool loadPLY( 
	const char *path, 
	std::vector<vec3> &out_vertices,
	std::vector<vec3> &out_color,
	vec3 *bound,
	unsigned char mirror
	)
{
	//Open file
	FILE *file = fopen(path, "r");

	//Failed to open file
	if (!file)
	{
		printf("Error opening file: %s\n", path);
		return false;
	}

	//Vector for vertices - save vertices/colors by index (from file)
	std::vector<vec3> temp_vertices, temp_color;

	//How many vertices and faces to expect (from PLY header)
	unsigned int vertices = 0, faces = 0;

	//Read each line (limited to 100 chars - I should change that sometime)
	char lineHeader[100];

	//Read words until end of file or end of header
	while ( fscanf(file, "%s", lineHeader) != EOF 
		&& strcmp(lineHeader, "end_header") != 0 )
	{
		//Found comment - Skip line
		if ( strcmp(lineHeader, "comment") == 0 )
			fscanf(file, "%*[^\n]"); else
		//Found an element
		if ( strcmp(lineHeader, "element") == 0 )
		{
			//Get next word
			fscanf(file, "%s", lineHeader);

			//Found vertices - save number of vertices
			if ( strcmp(lineHeader, "vertex") == 0 )
				fscanf(file, "%d", &vertices); else
			//Found faces - save number of faces
			if ( strcmp(lineHeader, "face") == 0 )
				fscanf(file, "%d", &faces);
		} 
	}

	//Read vertices
	unsigned int i;
	for (i = 0; i < vertices; i++)
	{
		//New vertex
		vec3 vertex;

		//RGB values (0-255)
		unsigned char r, g, b;

		//Read vertex and colors values
		fscanf(file, "%f %f %f %hhu %hhu %hhu",
			&vertex.x, &vertex.y, &vertex.z,
			&r, &g, &b);

		//Create color and convert 0-255 to 0.0-1.0
		vec3 color(r /255.0f, g /255.0f, b /255.0f);

		//Apply mirroring (if exists)
		if ( mirror == 1 )
			vertex.x = -vertex.x;
		if ( mirror == 2 )
			vertex.y = -vertex.y;
		if ( mirror == 4)
			vertex.z = -vertex.z;

		//Add new vertex and color to temporary vectors
		temp_vertices.push_back(vertex);
		temp_color   .push_back(color );

		//Calculate boundbox (if exists)
		if (bound)
		{
			if (bound->x < vertex.x) bound->x = vertex.x;
			if (bound->y < vertex.y) bound->y = vertex.y;
			if (bound->z < vertex.z) bound->z = vertex.z;
		}
	} 

	//For each face
	for (i = 0; i < faces; i++)
	{
		//Faces number
		int fn;
		fscanf(file, "%d", &fn);

		//Get all faces
		int temp[fn];
		int j;
		for (j = 0; j < fn; j++)
			fscanf(file, "%d", temp+j);

		//Apply first face
		out_vertices.push_back( temp_vertices[ temp[0] ]);
		out_vertices.push_back( temp_vertices[ temp[1] ]);
		out_vertices.push_back( temp_vertices[ temp[2] ]);
		if (&out_color)
		{
			out_color.push_back( temp_color[ temp[0] ]);
			out_color.push_back( temp_color[ temp[1] ]);
			out_color.push_back( temp_color[ temp[2] ]);
		}

		//For each extra face
		j = 2;
		while (j < fn-1)
		{
			//More than 1 extra vertices
			if (fn-1 -j > 1)
			{
				out_vertices.push_back( temp_vertices[ temp[j  ] ]);
				out_vertices.push_back( temp_vertices[ temp[j+1] ]);
				out_vertices.push_back( temp_vertices[ temp[j+2] ]);
				if (&out_color)
				{
					out_color.push_back( temp_color[ temp[j  ] ]);
					out_color.push_back( temp_color[ temp[j+1] ]);
					out_color.push_back( temp_color[ temp[j+2] ]);
				}
			}
			//Only 1 extra vertex - finish shape
			else
			{
				out_vertices.push_back( temp_vertices[ temp[j  ] ]);
				out_vertices.push_back( temp_vertices[ temp[j+1] ]);
				out_vertices.push_back( temp_vertices[ temp[ 0 ] ]);
				if (&out_color)
				{
					out_color.push_back( temp_color[ temp[j  ] ]);
					out_color.push_back( temp_color[ temp[j+1] ]);
					out_color.push_back( temp_color[ temp[ 0 ] ]);
				}
			}
			
			//Advance counter
			j += 2;
		}
	}

	//Close file
	fclose(file);

	//Success!
	return true;
}

// So far parses vertex xyz and colors rgb
mesh_data *loadPly(const char *path, int flags)
{
	//File
	FILE *f;
	if ( !(f = fopen(path, "r")) )
	{
		fprintf(stderr, "loadPly: failed to open file: %s: %s\n", 
			path, strerror(errno));
		return 0;
	}

	//Buffer
	char *buff = (char*)malloc(sizeof(char) *100);
	
	//Data
	unsigned int vertices = 0, faces = 0;
	std::vector<property> vert_prop;
	unsigned char face_ind_form, vert_ind_form;

	//Struct to return
	mesh_data *obj = new mesh_data;

	/* Check ply magic number:
	 * 		Get 3 first chars. (ignore anything on the rest of the line)
	 *		Add terminating byte.
	 *		Compare with "ply".
	 */
	if ( fscanf(f, "%3c%*[^\n]%*1c", buff) == EOF )
		goto error;

	buff[3] = '\0';
	if ( strcmp(buff, "ply") != 0 )
		goto error;

	/* Check format
	 * 		Get first word.
	 *		Compare with "format".
	 *			Get rest of line
	 *			Compare with "ascii 1.0" | "binary_little_endian 1.0"
	 				| "binary_big_endian 1.0"
	 *			Set `format` accordingly.
	 */
	char format;
	if ( fscanf(f, "%s", buff) == EOF )
		goto error;

	//Check format
	if ( strcmp(buff, "format") == 0 )
	{
		//Get actual format
		if ( fscanf(f, "%*c%[^\n]", buff) == EOF)
			goto error;

		//Ascii
		if ( strcmp(buff, "ascii 1.0") == 0 )
			format = 'a'; else
		//Little endian
		if ( strcmp(buff, "binary_little_endian 1.0") == 0 )
			format = 'l'; else
		//Big endian
		if ( strcmp(buff, "binary_big_endian 1.0") == 0 )
			format = 'b';
		//Error
		else goto error;
	}
	else goto error;

	/* What this parser reads:
	 *		element vertex
	 			property x
				property y
				property z
				property red
				property green
				property blue
	 *		element face
	 			property vertex_indices
	 */

	/* Get elements
	 * 		Read word-by-word.
	 *		Compare with "element"
	 *			Read next word.
	 *			Compare with "vertex"
	 *				Read next word as number of vertices to expect.
	 *			Compare with "faces"
	 *				Read mext wprd as number of faces to expect.
	 *		Compare with "property"
	 *			Read next word.
	 *			Compare with "list"
	 *				Read next word.
	 *				Get type amount of values from it.
	 *				Read next word.
	 *				Get type of actual values from it.
	 *				Read next word.
	 *				Hope that it's "vertex_indices"
	 *			else
	 *				Get property type (int, float, etc.)
	 *				Read next word.
	 *				Compare with x/y/z - red/green/blue
	 */

	//Read word by word
	while ( fscanf(f, "%s", buff) != EOF
		&& strcmp(buff, "end_header") != 0 )
	{
		//Found an element
		if ( strcmp(buff, "element") == 0 )
		{
			//This parser only reads vertices and faces
			if ( fscanf(f, "%s", buff) == EOF )
				goto error;

			//Vertex
			if ( strcmp(buff, "vertex") == 0 )
			{
				//Get vertex number
				if ( fscanf(f, "%u", &vertices) == EOF )
					goto error;
			} else
			//Face
			if ( strcmp(buff, "face") == 0 )
			{
				if ( fscanf(f, "%u", &faces) == EOF )
					goto error;
			} 
			//Unsupported element - ignore
			else continue;
		} else
		//Found property
		if ( strcmp(buff, "property") == 0 )
		{
			//Face's property
		/*	if ( faces != 0 )
			{
				//Get property type
				if (fscanf(f, "%s", buff) == EOF)
					goto error;

				//Ignore anything that is not a list
				if (strcmp(buff, "list") != 0)
				{
					if (fscanf(f, "%*[^\n]") == EOF)
						goto error;
					continue;
				}

				//Get face indices and vertex indices formats
				if (fscanf(f, "%s", buff) == EOF) goto error;
				face_ind_form = form_to_num(buff);
				if (fscanf(f, "%s", buff) == EOF) goto error;
				vert_ind_form = form_to_num(buff);

				//Get flag
				if (fscanf(f, "%s", buff) == EOF)
					goto error;

				//Property was vertex_indices - stop looking
				if (strcmp(buff, "vertex_indices") == 0)
					break;

				//Skip line
				if (fscanf(f, "%[^\n]", buff) == EOF)
					goto error;
			} else*/
			//Vertex's property
			if ( vertices != 0 )
			{
				//Init property
				property pr;

				//Get format
				if ( fscanf(f, "%s", buff) == EOF )
					goto error;
				pr.format = form_to_num( buff );

				//Get flag
				if ( fscanf(f, "%s", buff) == EOF )
					goto error;
				pr.flag = flag_to_num( buff );

				//Add property to vertex properties
				vert_prop.push_back(pr);

				//Skip line
				if ( fscanf(f, "%*[^\n]") == EOF )
					goto error;
			}
		}
		//Ignore unknown line (including comments)
		else
		{
			if ( fscanf(f, "%[^\n]", buff) == EOF )
			{
				printf("Ignored: %s\n", buff);
				goto error;
			}
		}
	} //Finished header


	printf("Parser is going to search for:\n");
	for (int i = 0; i < vert_prop.size(); i++)
	{
		printf("%s (%s)\n", flagstr(vert_prop[i].flag), formatstr(vert_prop[i].format));
	}
	
	return NULL;

	if (true)
	{
		char test[100] = "something";
		void *p;

		if ( ( p = malloc( sizeof(void*) *vert_prop.size() ) ) == NULL)
			goto error;

		unsigned int j;
		for (j = 0; j < vert_prop.size(); j++)
		{
			switch (vert_prop[j].flag)
			{
				//case FLG_V_POS_X:
				//	p[j] = &mesh_data.pos;
			}
			switch (vert_prop[j].format)
			{
				case PLY_CHAR:
					strcat(test, "%s");
			}
		}

		printf("Search string: %s\n", test);
	}

	//float *temp_vertices;
	//if (temp_vertices = malloc() == NULL)
	//	goto error;

	//Print all data gatheres so far
	unsigned int i;
	for (i = 0; i < vert_prop.size(); i++)
	{
		printf("Vertex property: %d\n", i);
		printf("format: %d | flag: %d\n", vert_prop[i].format, vert_prop[i].flag);
	}

	printf("Face indices format: %d\n"  , face_ind_form);
	printf("Vertex indices format: %d\n", vert_ind_form);

	//Close file
	fclose(f);

	return obj;

	//Error
	error:
	if (ferror(f))
		fprintf(stderr, "loadPly: error while parsing: %s: %s\n", path,
			strerror(errno)); else
	if (feof(f))
		printf("loadPly: unexpected end of file: %s\n", path);
	else
		printf("loadPly: unexpected error: %s\n", path);
	fclose(f);
	return 0;
}

//Turns the read format to a number (specified by defines on loadModel.h)
unsigned char form_to_num( char *format )
{
	if (strcmp(format, "char") == 0 
	||  strcmp(format, "int8") == 0 )
		return PLY_CHAR;

	if (strcmp(format, "uchar") == 0 
	||  strcmp(format, "uint8") == 0 )
		return PLY_UCHAR;

	if (strcmp(format, "short") == 0 
	||  strcmp(format, "int16") == 0 )
		return PLY_SHORT;

	if (strcmp(format, "ushort") == 0 
	||  strcmp(format, "uint16") == 0 )
		return PLY_USHORT;

	if (strcmp(format, "int"  ) == 0 
	||  strcmp(format, "int32") == 0 )
		return PLY_INT;

	if (strcmp(format, "uint"  ) == 0 
	||  strcmp(format, "uint32") == 0 )
		return PLY_UINT;

	if (strcmp(format, "float"  ) == 0 
	||  strcmp(format, "float32") == 0 )
		return PLY_FLOAT;

	if (strcmp(format, "double" ) == 0 
	||  strcmp(format, "float64") == 0 )
		return PLY_DOUBLE;

	return PLY_UNSUPPORTED;
}

unsigned int flag_to_num( char *flag )
{
	if (strcmp(flag, "x") == 0)
		return FLG_V_POS_X;

	if (strcmp(flag, "y") == 0)
		return FLG_V_POS_Y;

	if (strcmp(flag, "z") == 0)
		return FLG_V_POS_Z;

	if (strcmp(flag, "red") == 0)
		return FLG_V_COL_R;

	if (strcmp(flag, "green") == 0)
		return FLG_V_COL_G;

	if (strcmp(flag, "blue") == 0)
		return FLG_V_COL_B;

	return 0;
}

char *flagstr( unsigned int flag )
{
	switch (flag)
	{
		case FLG_V_POS_X: return "vertex position x";
		case FLG_V_POS_Y: return "vertex position y";
		case FLG_V_POS_Z: return "vertex position z";
		case FLG_V_COL_R: return "vertex color red";
		case FLG_V_COL_G: return "vertex color green";
		case FLG_V_COL_B: return "vertex color blue";
		case FLG_F_VINDICES: return "vertex indices";
	}
	return "Nothingness";
}

char *formatstr( char format )
{
	switch (format)
	{
		case PLY_CHAR: return "char";
		case PLY_UCHAR: return "unsigned char";
		case PLY_SHORT: return "short";
		case PLY_USHORT: return "unsigned short";
		case PLY_INT: return "int";
		case PLY_UINT: return "unsigned int";
		case PLY_FLOAT: return "float";
		case PLY_DOUBLE: return "double";
	}
	return "Unsupported";
}
